﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Linq.Expressions;
using SQLExpressionBuilder;

namespace SimpleExpression
{
    public class EntityExpression : Expression
    {
        private EntityMetadata entityMetadata;
        private ColumnSetExpression columnSetExpression;
        private JoinEntityExpression joinEntityExpression;
        private FilterExpression filterExpression;
        private GroupByExpression groupByExpression;
        private OrderExpression orderExpression;
        private PagingExpression pagingExpression;

        private bool retrieveAsDynamic = false;

        //XML Required
        public EntityExpression()
        {

        }

        public EntityExpression(string entityMetadata)
            :this(EntityMetadata.GetEntity(entityMetadata))
        { 
        
        }

        public EntityExpression(EntityMetadata entityMetadata)
        {
            this.entityMetadata = entityMetadata;
            this.entityMetadata.Alias = "T0";

            this.pagingExpression = new PagingExpression();
            this.columnSetExpression = new ColumnSetExpression(entityMetadata);
            this.joinEntityExpression = new JoinEntityExpression(entityMetadata);
            this.filterExpression = new FilterExpression(entityMetadata);
            this.groupByExpression = new GroupByExpression(entityMetadata);
            this.orderExpression = new OrderExpression(entityMetadata);
        }

        public PagingExpression PagingExpression
        {
            get { return this.pagingExpression; }
            set { this.pagingExpression = value; }
        }

        public ColumnSetExpression ColumnSetExpression
        {
            get { return this.columnSetExpression; }
            set { this.columnSetExpression = value; }
        }

        public JoinEntityExpression JoinEntityExpression
        {
            get { return this.joinEntityExpression; }
            set { this.joinEntityExpression = value; }
        }

        public FilterExpression FilterExpression
        {
            get { return this.filterExpression; }
            set { this.filterExpression = value; }
        }

        public GroupByExpression GroupByExpression
        {
            get { return this.groupByExpression; }
            set { this.groupByExpression = value; }
        }

        public OrderExpression OrderExpression
        {
            get { return this.orderExpression; }
            set { this.orderExpression = value; }
        }

        public EntityMetadata EntityMetadata
        {
            get { return this.entityMetadata; }
        }

        public bool RetrieveAsDynamic
        {
            get { return this.retrieveAsDynamic; }
            set { this.retrieveAsDynamic = value; }
        }

        #region Expression

        public override void Accept(ExpressionVisitor visitor)
        {
            visitor.Visit(this);
        }

        #endregion

        #region Generic

        public EntityExpression Select<TEntity, TResult>(System.Linq.Expressions.Expression<Func<TEntity, TResult>> expression)
        {
            if (expression.Body is NewExpression)
                this.AddToColumnSet((NewExpression)expression.Body);
            else
                if (expression.Body is MemberExpression)
                    this.AddToColumnSet((MemberExpression)expression.Body);

            return this;
        }

        public EntityExpression Where<TEntity>(Expression<Func<TEntity, bool>> expression)
        {
            LinqExpressionVisitor visitor = new LinqExpressionVisitor(this.entityMetadata);
            this.filterExpression = (FilterExpression)visitor.Visit(expression);

            return this;
        }

        public EntityExpression Group<TEntity, TResult>(System.Linq.Expressions.Expression<Func<TEntity, TResult>> expression)
        {
            if (expression.Body is NewExpression)
                this.AddGroupBy((NewExpression)expression.Body);
            else
                if (expression.Body is MemberExpression)
                    this.AddGroupBy((MemberExpression)expression.Body);

            return this;
        }

        public EntityExpression Order<TEntity, TResult>(System.Linq.Expressions.Expression<Func<TEntity, TResult>> expression)
        {
            if (expression.Body is NewExpression)
                this.AddOrderBy((NewExpression)expression.Body);
            else
                if (expression.Body is MemberExpression)
                    this.AddOrderBy((MemberExpression)expression.Body);

            return this;
        }

        #endregion

        #region Helper Methods

        protected void AddToColumnSet(NewExpression expression)
        {
            foreach (MemberExpression item in expression.Arguments)
            {
                this.AddToColumnSet(item);
            }
        }

        protected void AddToColumnSet(MemberExpression expression)
        {
            //Se verifica que no se haya agregado la columna
            //if(this.ColumnSetExpression.Attributes.Where( c => c.PropertyMetadata.PlatformName == expression.Member.Name).Count() == 0)
            this.ColumnSetExpression.Attributes.Add(GetAttributeExpression(expression.Member.Name));
        }

        protected void AddGroupBy(NewExpression expression)
        {
            foreach (MemberExpression item in expression.Arguments)
            {
                this.AddGroupBy(item);
            }
        }

        protected void AddGroupBy(MemberExpression expression)
        {
            //Se verifica que no se haya agregado la columna
            //if (this.OrderExpression.Properties.Where(c => c.Key.PropertyMetadata.PlatformName == expression.Member.Name).Count() == 0)
            this.GroupByExpression.Add(GetAttributeExpression(expression.Member.Name));
        }

        protected void AddOrderBy(NewExpression expression)
        {
            foreach (MemberExpression item in expression.Arguments)
            {
                this.AddOrderBy(item);
            }
        }

        protected void AddOrderBy(MemberExpression expression)
        {
            //Se verifica que no se haya agregado la columna
            //if (this.OrderExpression.Properties.Where(c => c.Key.PropertyMetadata.PlatformName == expression.Member.Name).Count() == 0)
            this.OrderExpression.Add(GetAttributeExpression(expression.Member.Name), OrderType.ASC);
        }

        private AttributeExpression GetAttributeExpression(string name)
        {
            // Si se encuentra el physicalName entonces se agrega un AttributeExpression de lo contrario un ViewAttributeExpression
            switch (this.EntityMetadata.Properties.Contains(name))
            {
                case MappingType.None:
                    break;
                case MappingType.Physical:
                    return new AttributeExpression(this.EntityMetadata[name]);
                case MappingType.Platform:
                    return new ViewAttributeExpression(this.EntityMetadata.Properties[name, MappingType.Platform]);
                default:
                    break;
            }

            throw new NotImplementedException();
        }

        #endregion

    }

    public class EntityExpression<Entity> : EntityExpression
        where Entity : class
    {
        public EntityExpression(EntityMetadata entityMetadata)
            : base(entityMetadata)
        {

        }

        public EntityExpression<Entity> Select<Property>(System.Linq.Expressions.Expression<Func<Entity, Property>> expression)
        {
            base.Select(expression);
            
            return this;
        }

        public EntityExpression<Entity> Where(System.Linq.Expressions.Expression<Func<Entity, bool>> expression)
        {
            base.Where(expression);

            return this;
        }

        public EntityExpression<Entity> Group<Property>(System.Linq.Expressions.Expression<Func<Entity, Property>> expression)
        {
            base.Group(expression);

            return this;
        }

        public EntityExpression<Entity> Order<Property>(System.Linq.Expressions.Expression<Func<Entity, Property>> expression)
        {
            base.Order(expression);

            return this;
        }
    }
}